{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# エポック数が10だとトレーニングに時間が掛かりすぎるため、ここでは2にしていますが、\n",
    "# より高い精度が出ることを体験したい方はより大きなエポック数を試してみてください。\n",
    "# epochs = 10\n",
    "epochs = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 6 - Federated Learningを使ってMNIST\n",
    "\n",
    "MNISTは手書き文字のデータセットです。CNNを使った分類モデルをトレーニングします。\n",
    "\n",
    "## 10行でPytorchのチュートリアルをPyTorch + PySyftへアップグレード\n",
    "\n",
    "\n",
    "### 背景 \n",
    "\n",
    "Federated Learningは分散配置された学習データで学習を行える、とてもエキサイティングで、今まさに盛り上がりつつある機械学習のテクニックです。学習データはデータ所有者（ここではワーカー）の元を離れず、モデルの方がワーカー間で共有されながら学習されていくという考え方です。この手法の利点は、データのプライバシーを守れる事です。アプリケーションの応用例としては、キーボードの予測入力があります。キーボードの予測入力ではあなたが入力するテキストを学習データとする必要がありますが、個人的なメッセージですから、サーバーに送りたくはないですよね！\n",
    "\n",
    "ところで、Federated Learningが注目を集め初めているのは、個人情報の保護に関する意識の高まりと関係があります。2018年の5月にEUで施工されたGDPRをきっかけに一躍注目を集めるようになりました。法規制を見越して、アップルやグーグルはこの技術に大きな投資をしています。特にモバイルユーザーのプライバシーの保護を意識しています。しかしながら、彼らはソースコードをオープンにしていません。\n",
    "\n",
    "私たち（OpenMined）は、機械学習に携わる者なら、誰でも簡単にプライバシーに配慮した学習手法にアクセスできるべきだと考えています。そこで私たちはたったの一行のコードでデータを暗号化できるツールを開発しました。また、PyTorch 1.0の新機能を使って、直感的に、セキュアに、かつ大規模にFederated Learningを実装できるフレームワークもリリースしました。\n",
    "[詳細はブログを参照してください](https://blog.openmined.org/training-cnns-using-spdz/)\n",
    "\n",
    "このチュートリアルでは、[Pytorchの（公式）チュートリアルをベース](https://github.com/pytorch/examples/blob/master/mnist/main.py)に、[PySyftのライブラリ](https://github.com/OpenMined/PySyft/)を使う事で簡単にFederated Learningを実装できる事を紹介します。チュートリアルのコードサンプルを元に、Federated Learning化するために必要な変更を、一行一行確認していきましょう。\n",
    "\n",
    "このコンテンツは[私たちのブログ](https://blog.openmined.org/upgrade-to-federated-learning-in-10-lines)からも見つけることが可能です。\n",
    "\n",
    "Authors:\n",
    "- Théo Ryffel - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "**Ok, let's get started!**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 必要なモデルやライブラリをインポート\n",
    "\n",
    "まず、PyTorch関連のライブラリをインポートします。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "次にPySyft関連の設定を行います。ここではリモートワーカーとして`alice`と`bob`を定義しています。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:516: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint8 = np.dtype([(\"qint8\", np.int8, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:517: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_quint8 = np.dtype([(\"quint8\", np.uint8, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:518: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint16 = np.dtype([(\"qint16\", np.int16, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:519: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_quint16 = np.dtype([(\"quint16\", np.uint16, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:520: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint32 = np.dtype([(\"qint32\", np.int32, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorflow/python/framework/dtypes.py:525: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  np_resource = np.dtype([(\"resource\", np.ubyte, 1)])\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tf_encrypted/session.py:24: The name tf.Session is deprecated. Please use tf.compat.v1.Session instead.\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:541: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint8 = np.dtype([(\"qint8\", np.int8, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:542: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_quint8 = np.dtype([(\"quint8\", np.uint8, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:543: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint16 = np.dtype([(\"qint16\", np.int16, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:544: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_quint16 = np.dtype([(\"quint16\", np.uint16, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:545: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  _np_qint32 = np.dtype([(\"qint32\", np.int32, 1)])\n",
      "/home/ext-share/anaconda3/envs/pytorch/lib/python3.6/site-packages/tensorboard/compat/tensorflow_stub/dtypes.py:550: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.\n",
      "  np_resource = np.dtype([(\"resource\", np.ubyte, 1)])\n"
     ]
    }
   ],
   "source": [
    "import syft as sy  # <-- NEW: Pysyftライブラリのインポート\n",
    "hook = sy.TorchHook(torch)  # <-- NEW: PyTorchをホック（Federated Learningに必要な機能を追加）\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")  # <-- NEW: リモートワーカー、Bobを追加\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")  # <-- NEW: 同じくAliceを追加"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "学習処理のハイパーパラメータを定義します。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 1000\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.01\n",
    "        self.momentum = 0.5\n",
    "        self.no_cuda = False\n",
    "        self.seed = 1\n",
    "        self.log_interval = 30\n",
    "        self.save_model = False\n",
    "\n",
    "args = Arguments()\n",
    "\n",
    "use_cuda = not args.no_cuda and torch.cuda.is_available()\n",
    "\n",
    "torch.manual_seed(args.seed)\n",
    "\n",
    "device = torch.device(\"cuda\" if use_cuda else \"cpu\")\n",
    "\n",
    "kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### データをロードして、ワーカーへ送る\n",
    "\n",
    "まず、データをロードし、`.federate`コマンドを使って、データを分割しつつ、PytorchのDataset型からPySyftのFederated Dataset型へ変更し、複数のワーカー（このケースではAliceとBob）に割り当てます。この際に出来上がったfederated datasetはFederated DataLoaderへ渡されます。テスト用のデータセットに変更はありません。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:root:The following options are not supported: num_workers: 1, pin_memory: True\n"
     ]
    }
   ],
   "source": [
    "federated_train_loader = sy.FederatedDataLoader( # <-- FederatedDataLoader を使います\n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ]))\n",
    "    .federate((bob, alice)), # <-- NEW: FederatedDatasetに変換し、分割してワーカーへ送ります。\n",
    "    batch_size=args.batch_size, shuffle=True, **kwargs)\n",
    "\n",
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False, transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True, **kwargs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### CNNモデルの定義\n",
    "\n",
    "ここではPytorchの公式チュートリアルの事例と全く同じ設定とします。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 20, 5, 1)\n",
    "        self.conv2 = nn.Conv2d(20, 50, 5, 1)\n",
    "        self.fc1 = nn.Linear(4*4*50, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.conv1(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = F.relu(self.conv2(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = x.view(-1, 4*4*50)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### トレーニング関数とテスト関数の定義\n",
    "\n",
    "トレーニング時は、データが`alice`と`bob`に分散しているので、モデルを適宜各ワーカーへ送る必要があります。モデルを各ワーカーへ送った後は、ごく普通のPyTorchのトレーニングスクリプトと同様の構文で、リモートマシンでの学習を行うことができます。トレーニング完了後は、ロスと学習済みモデルを受け取ります。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(args, model, device, federated_train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- now FederatedDataLoaderです\n",
    "        model.send(data.location) # <-- NEW: モデルをデータ所有者の元へ送ります\n",
    "        data, target = data.to(device), target.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        model.get() # <-- NEW: 学習済みモデルを受け取ります\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            loss = loss.get() # <-- NEW: ログ表示用にロスを受け取ります\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(federated_train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "テスト用の関数は変更の必要はありません。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, device, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            data, target = data.to(device), target.to(device)\n",
    "            output = model(data)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # バッチロスを合計します\n",
    "            pred = output.argmax(1, keepdim=True) # log-probabilityが最大のインデックスを取得します\n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 実際にトレーニングしてみます"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train Epoch: 1 [0/60032 (0%)]\tLoss: 2.305134\n",
      "Train Epoch: 1 [1920/60032 (3%)]\tLoss: 2.156802\n",
      "Train Epoch: 1 [3840/60032 (6%)]\tLoss: 1.896626\n",
      "Train Epoch: 1 [5760/60032 (10%)]\tLoss: 1.440404\n",
      "Train Epoch: 1 [7680/60032 (13%)]\tLoss: 0.866800\n",
      "Train Epoch: 1 [9600/60032 (16%)]\tLoss: 0.654367\n",
      "Train Epoch: 1 [11520/60032 (19%)]\tLoss: 0.593107\n",
      "Train Epoch: 1 [13440/60032 (22%)]\tLoss: 0.455813\n",
      "Train Epoch: 1 [15360/60032 (26%)]\tLoss: 0.370645\n",
      "Train Epoch: 1 [17280/60032 (29%)]\tLoss: 0.303963\n",
      "Train Epoch: 1 [19200/60032 (32%)]\tLoss: 0.313645\n",
      "Train Epoch: 1 [21120/60032 (35%)]\tLoss: 0.369348\n",
      "Train Epoch: 1 [23040/60032 (38%)]\tLoss: 0.237722\n",
      "Train Epoch: 1 [24960/60032 (42%)]\tLoss: 0.187720\n",
      "Train Epoch: 1 [26880/60032 (45%)]\tLoss: 0.524170\n",
      "Train Epoch: 1 [28800/60032 (48%)]\tLoss: 0.224550\n",
      "Train Epoch: 1 [30720/60032 (51%)]\tLoss: 0.143592\n",
      "Train Epoch: 1 [32640/60032 (54%)]\tLoss: 0.268505\n",
      "Train Epoch: 1 [34560/60032 (58%)]\tLoss: 0.187220\n",
      "Train Epoch: 1 [36480/60032 (61%)]\tLoss: 0.302562\n",
      "Train Epoch: 1 [38400/60032 (64%)]\tLoss: 0.239577\n",
      "Train Epoch: 1 [40320/60032 (67%)]\tLoss: 0.256233\n",
      "Train Epoch: 1 [42240/60032 (70%)]\tLoss: 0.192204\n",
      "Train Epoch: 1 [44160/60032 (74%)]\tLoss: 0.174780\n",
      "Train Epoch: 1 [46080/60032 (77%)]\tLoss: 0.221893\n",
      "Train Epoch: 1 [48000/60032 (80%)]\tLoss: 0.323514\n",
      "Train Epoch: 1 [49920/60032 (83%)]\tLoss: 0.274752\n",
      "Train Epoch: 1 [51840/60032 (86%)]\tLoss: 0.130486\n",
      "Train Epoch: 1 [53760/60032 (90%)]\tLoss: 0.184121\n",
      "Train Epoch: 1 [55680/60032 (93%)]\tLoss: 0.223131\n",
      "Train Epoch: 1 [57600/60032 (96%)]\tLoss: 0.080876\n",
      "Train Epoch: 1 [59520/60032 (99%)]\tLoss: 0.143369\n",
      "\n",
      "Test set: Average loss: 0.1573, Accuracy: 9515/10000 (95%)\n",
      "\n",
      "Train Epoch: 2 [0/60032 (0%)]\tLoss: 0.102808\n",
      "Train Epoch: 2 [1920/60032 (3%)]\tLoss: 0.106100\n",
      "Train Epoch: 2 [3840/60032 (6%)]\tLoss: 0.146959\n",
      "Train Epoch: 2 [5760/60032 (10%)]\tLoss: 0.148886\n",
      "Train Epoch: 2 [7680/60032 (13%)]\tLoss: 0.109027\n",
      "Train Epoch: 2 [9600/60032 (16%)]\tLoss: 0.110443\n",
      "Train Epoch: 2 [11520/60032 (19%)]\tLoss: 0.118914\n",
      "Train Epoch: 2 [13440/60032 (22%)]\tLoss: 0.062979\n",
      "Train Epoch: 2 [15360/60032 (26%)]\tLoss: 0.089123\n",
      "Train Epoch: 2 [17280/60032 (29%)]\tLoss: 0.156774\n",
      "Train Epoch: 2 [19200/60032 (32%)]\tLoss: 0.161360\n",
      "Train Epoch: 2 [21120/60032 (35%)]\tLoss: 0.157510\n",
      "Train Epoch: 2 [23040/60032 (38%)]\tLoss: 0.229683\n",
      "Train Epoch: 2 [24960/60032 (42%)]\tLoss: 0.196785\n",
      "Train Epoch: 2 [26880/60032 (45%)]\tLoss: 0.206010\n",
      "Train Epoch: 2 [28800/60032 (48%)]\tLoss: 0.079425\n",
      "Train Epoch: 2 [30720/60032 (51%)]\tLoss: 0.062955\n",
      "Train Epoch: 2 [32640/60032 (54%)]\tLoss: 0.158972\n",
      "Train Epoch: 2 [34560/60032 (58%)]\tLoss: 0.156671\n",
      "Train Epoch: 2 [36480/60032 (61%)]\tLoss: 0.074501\n",
      "Train Epoch: 2 [38400/60032 (64%)]\tLoss: 0.161591\n",
      "Train Epoch: 2 [40320/60032 (67%)]\tLoss: 0.073496\n",
      "Train Epoch: 2 [42240/60032 (70%)]\tLoss: 0.152694\n",
      "Train Epoch: 2 [44160/60032 (74%)]\tLoss: 0.047764\n",
      "Train Epoch: 2 [46080/60032 (77%)]\tLoss: 0.085315\n",
      "Train Epoch: 2 [48000/60032 (80%)]\tLoss: 0.100825\n",
      "Train Epoch: 2 [49920/60032 (83%)]\tLoss: 0.154736\n",
      "Train Epoch: 2 [51840/60032 (86%)]\tLoss: 0.031952\n",
      "Train Epoch: 2 [53760/60032 (90%)]\tLoss: 0.073943\n",
      "Train Epoch: 2 [55680/60032 (93%)]\tLoss: 0.113156\n",
      "Train Epoch: 2 [57600/60032 (96%)]\tLoss: 0.112269\n",
      "Train Epoch: 2 [59520/60032 (99%)]\tLoss: 0.068695\n",
      "\n",
      "Test set: Average loss: 0.0901, Accuracy: 9737/10000 (97%)\n",
      "\n",
      "CPU times: user 1min 38s, sys: 1.14 s, total: 1min 39s\n",
      "Wall time: 1min 43s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "model = Net().to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentumは現在サポートされていません\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, device, federated_train_loader, optimizer, epoch)\n",
    "    test(args, model, device, test_loader)\n",
    "\n",
    "if (args.save_model):\n",
    "    torch.save(model.state_dict(), \"mnist_cnn.pt\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ジャジャーン！ Federated Learningを使ってリモートデータでのモデル学習に成功しました！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 最後に\n",
    "\n",
    "気に掛かっている事はありませんか: **学習にかかる時間です** Federated Learningでの学習って通常の学習より時間が掛かりそうな気がするけど、どの程度長く掛かっちゃうのか気になりませんか？\n",
    "\n",
    "コンピューテーションにかかる時間は、**もちろん通常の学習よりは時間がかかるけれど倍までは行かない**というものです。だいたい1.9倍くらいの時間が掛かります。でも、得られるメリットを考えたら小さなマイナスですよね。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 結論\n",
    "\n",
    "見て頂いた通り、Pytorchの公式チュートリアルのソースコードを10行程度変更するだけで、Federated Learningを使ってMNISTを学習することができました。\n",
    "\n",
    "もちろん、改善の余地はまだまだあります。各ワーカーのコンピューテーションを並列化するとか、バッチ毎に集計をするのではなく、数バッチに1回だけ集計をするようにするとか、ワーカーどうしのやりとりの頻度を減らすとか、色々あります。これらは、Federated Learningをプロダクション環境で使えるようにするために私たちが取り組んでいる機能です。それらの機能がリリースされしだい、チュートリアルにも反映させていきたいと思います。\n",
    "\n",
    "もし、やろうと思えば、ご自身でもFederated Learningを実装できると思います。もし、PySyft、プライバシーに配慮したディープラーニング、非中央集権的なAIの学習データ、あるいは学習データのサプライチェーンに関する活動に参加したい、貢献したいって思われた方は以下を参考にしてみてください。\n",
    "\n",
    "### PySyftのGitHubレポジトリにスターをつける\n",
    "\n",
    "一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Slackに入る\n",
    "\n",
    "最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。\n",
    "下記フォームから入る事ができます。\n",
    "[http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### コードプロジェクトに参加する\n",
    "\n",
    "コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、\"Projects\"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、\"good first issue\"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### 寄付\n",
    "\n",
    "もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
